package com.yzy.sociallib.utils;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

/**
 * description :
 * @date 2019/7/15
 * @author: yzy.
 */
public class FileIOUtils {

  private static int sBufferSize = 8192;

  private FileIOUtils() {
    throw new UnsupportedOperationException("u can't instantiate me...");
  }

  /**
   * Write file from input stream.
   *
   * @param filePath The path of file.
   * @param is       The input stream.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromIS(final String filePath, final InputStream is) {
    return writeFileFromIS(getFileByPath(filePath), is, false);
  }

  /**
   * Write file from input stream.
   *
   * @param filePath The path of file.
   * @param is       The input stream.
   * @param append   True to append, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromIS(final String filePath,
      final InputStream is,
      final boolean append) {
    return writeFileFromIS(getFileByPath(filePath), is, append);
  }

  /**
   * Write file from input stream.
   *
   * @param file The file.
   * @param is   The input stream.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromIS(final File file, final InputStream is) {
    return writeFileFromIS(file, is, false);
  }

  /**
   * Write file from input stream.
   *
   * @param file   The file.
   * @param is     The input stream.
   * @param append True to append, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromIS(final File file,
      final InputStream is,
      final boolean append) {
    if (!createOrExistsFile(file) || is == null) return false;
    OutputStream os = null;
    try {
      os = new BufferedOutputStream(new FileOutputStream(file, append));
      byte[] data = new byte[sBufferSize];
      int len;
      while ((len = is.read(data, 0, sBufferSize)) != -1) {
        os.write(data, 0, len);
      }
      return true;
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        if (os != null) {
          os.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Write file from bytes by stream.
   *
   * @param filePath The path of file.
   * @param bytes    The bytes.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByStream(final String filePath, final byte[] bytes) {
    return writeFileFromBytesByStream(getFileByPath(filePath), bytes, false);
  }

  /**
   * Write file from bytes by stream.
   *
   * @param filePath The path of file.
   * @param bytes    The bytes.
   * @param append   True to append, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByStream(final String filePath,
      final byte[] bytes,
      final boolean append) {
    return writeFileFromBytesByStream(getFileByPath(filePath), bytes, append);
  }

  /**
   * Write file from bytes by stream.
   *
   * @param file  The file.
   * @param bytes The bytes.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByStream(final File file, final byte[] bytes) {
    return writeFileFromBytesByStream(file, bytes, false);
  }

  /**
   * Write file from bytes by stream.
   *
   * @param file   The file.
   * @param bytes  The bytes.
   * @param append True to append, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByStream(final File file,
      final byte[] bytes,
      final boolean append) {
    if (bytes == null || !createOrExistsFile(file)) return false;
    BufferedOutputStream bos = null;
    try {
      bos = new BufferedOutputStream(new FileOutputStream(file, append));
      bos.write(bytes);
      return true;
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    } finally {
      try {
        if (bos != null) {
          bos.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Write file from bytes by channel.
   *
   * @param filePath The path of file.
   * @param bytes    The bytes.
   * @param isForce  是否写入文件
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByChannel(final String filePath,
      final byte[] bytes,
      final boolean isForce) {
    return writeFileFromBytesByChannel(getFileByPath(filePath), bytes, false, isForce);
  }

  /**
   * Write file from bytes by channel.
   *
   * @param filePath The path of file.
   * @param bytes    The bytes.
   * @param append   True to append, false otherwise.
   * @param isForce  True to force write file, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByChannel(final String filePath,
      final byte[] bytes,
      final boolean append,
      final boolean isForce) {
    return writeFileFromBytesByChannel(getFileByPath(filePath), bytes, append, isForce);
  }

  /**
   * Write file from bytes by channel.
   *
   * @param file    The file.
   * @param bytes   The bytes.
   * @param isForce True to force write file, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByChannel(final File file,
      final byte[] bytes,
      final boolean isForce) {
    return writeFileFromBytesByChannel(file, bytes, false, isForce);
  }

  /**
   * Write file from bytes by channel.
   *
   * @param file    The file.
   * @param bytes   The bytes.
   * @param append  True to append, false otherwise.
   * @param isForce True to force write file, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByChannel(final File file,
      final byte[] bytes,
      final boolean append,
      final boolean isForce) {
    if (bytes == null) return false;
    FileChannel fc = null;
    try {
      fc = new FileOutputStream(file, append).getChannel();
      fc.position(fc.size());
      fc.write(ByteBuffer.wrap(bytes));
      if (isForce) fc.force(true);
      return true;
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    } finally {
      try {
        if (fc != null) {
          fc.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Write file from bytes by map.
   *
   * @param filePath The path of file.
   * @param bytes    The bytes.
   * @param isForce  True to force write file, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByMap(final String filePath,
      final byte[] bytes,
      final boolean isForce) {
    return writeFileFromBytesByMap(filePath, bytes, false, isForce);
  }

  /**
   * Write file from bytes by map.
   *
   * @param filePath The path of file.
   * @param bytes    The bytes.
   * @param append   True to append, false otherwise.
   * @param isForce  True to force write file, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByMap(final String filePath,
      final byte[] bytes,
      final boolean append,
      final boolean isForce) {
    return writeFileFromBytesByMap(getFileByPath(filePath), bytes, append, isForce);
  }

  /**
   * Write file from bytes by map.
   *
   * @param file    The file.
   * @param bytes   The bytes.
   * @param isForce True to force write file, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByMap(final File file,
      final byte[] bytes,
      final boolean isForce) {
    return writeFileFromBytesByMap(file, bytes, false, isForce);
  }

  /**
   * Write file from bytes by map.
   *
   * @param file    The file.
   * @param bytes   The bytes.
   * @param append  True to append, false otherwise.
   * @param isForce True to force write file, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromBytesByMap(final File file,
      final byte[] bytes,
      final boolean append,
      final boolean isForce) {
    if (bytes == null || !createOrExistsFile(file)) return false;
    FileChannel fc = null;
    try {
      fc = new FileOutputStream(file, append).getChannel();
      MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), bytes.length);
      mbb.put(bytes);
      if (isForce) mbb.force();
      return true;
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    } finally {
      try {
        if (fc != null) {
          fc.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Write file from string.
   *
   * @param filePath The path of file.
   * @param content  The string of content.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromString(final String filePath, final String content) {
    return writeFileFromString(getFileByPath(filePath), content, false);
  }

  /**
   * Write file from string.
   *
   * @param filePath The path of file.
   * @param content  The string of content.
   * @param append   True to append, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromString(final String filePath,
      final String content,
      final boolean append) {
    return writeFileFromString(getFileByPath(filePath), content, append);
  }

  /**
   * Write file from string.
   *
   * @param file    The file.
   * @param content The string of content.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromString(final File file, final String content) {
    return writeFileFromString(file, content, false);
  }

  /**
   * Write file from string.
   *
   * @param file    The file.
   * @param content The string of content.
   * @param append  True to append, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  public static boolean writeFileFromString(final File file,
      final String content,
      final boolean append) {
    if (file == null || content == null) return false;
    if (!createOrExistsFile(file)) return false;
    BufferedWriter bw = null;
    try {
      bw = new BufferedWriter(new FileWriter(file, append));
      bw.write(content);
      return true;
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    } finally {
      try {
        if (bw != null) {
          bw.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  ///////////////////////////////////////////////////////////////////////////
  // the divide line of write and read
  ///////////////////////////////////////////////////////////////////////////

  /**
   * Return the lines in file.
   *
   * @param filePath The path of file.
   * @return the lines in file
   */
  public static List<String> readFile2List(final String filePath) {
    return readFile2List(getFileByPath(filePath), null);
  }

  /**
   * Return the lines in file.
   *
   * @param filePath    The path of file.
   * @param charsetName The name of charset.
   * @return the lines in file
   */
  public static List<String> readFile2List(final String filePath, final String charsetName) {
    return readFile2List(getFileByPath(filePath), charsetName);
  }

  /**
   * Return the lines in file.
   *
   * @param file The file.
   * @return the lines in file
   */
  public static List<String> readFile2List(final File file) {
    return readFile2List(file, 0, 0x7FFFFFFF, null);
  }

  /**
   * Return the lines in file.
   *
   * @param file        The file.
   * @param charsetName The name of charset.
   * @return the lines in file
   */
  public static List<String> readFile2List(final File file, final String charsetName) {
    return readFile2List(file, 0, 0x7FFFFFFF, charsetName);
  }

  /**
   * Return the lines in file.
   *
   * @param filePath The path of file.
   * @param st       The line's index of start.
   * @param end      The line's index of end.
   * @return the lines in file
   */
  public static List<String> readFile2List(final String filePath, final int st, final int end) {
    return readFile2List(getFileByPath(filePath), st, end, null);
  }

  /**
   * Return the lines in file.
   *
   * @param filePath    The path of file.
   * @param st          The line's index of start.
   * @param end         The line's index of end.
   * @param charsetName The name of charset.
   * @return the lines in file
   */
  public static List<String> readFile2List(final String filePath,
      final int st,
      final int end,
      final String charsetName) {
    return readFile2List(getFileByPath(filePath), st, end, charsetName);
  }

  /**
   * Return the lines in file.
   *
   * @param file The file.
   * @param st   The line's index of start.
   * @param end  The line's index of end.
   * @return the lines in file
   */
  public static List<String> readFile2List(final File file, final int st, final int end) {
    return readFile2List(file, st, end, null);
  }

  /**
   * Return the lines in file.
   *
   * @param file        The file.
   * @param st          The line's index of start.
   * @param end         The line's index of end.
   * @param charsetName The name of charset.
   * @return the lines in file
   */
  public static List<String> readFile2List(final File file,
      final int st,
      final int end,
      final String charsetName) {
    if (!isFileExists(file)) return null;
    if (st > end) return null;
    BufferedReader reader = null;
    try {
      String line;
      int curLine = 1;
      List<String> list = new ArrayList<>();
      if (isSpace(charsetName)) {
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
      } else {
        reader = new BufferedReader(
            new InputStreamReader(new FileInputStream(file), charsetName)
        );
      }
      while ((line = reader.readLine()) != null) {
        if (curLine > end) break;
        if (st <= curLine && curLine <= end) list.add(line);
        ++curLine;
      }
      return list;
    } catch (IOException e) {
      e.printStackTrace();
      return null;
    } finally {
      try {
        if (reader != null) {
          reader.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Return the string in file.
   *
   * @param filePath The path of file.
   * @return the string in file
   */
  public static String readFile2String(final String filePath) {
    return readFile2String(getFileByPath(filePath), null);
  }

  /**
   * Return the string in file.
   *
   * @param filePath    The path of file.
   * @param charsetName The name of charset.
   * @return the string in file
   */
  public static String readFile2String(final String filePath, final String charsetName) {
    return readFile2String(getFileByPath(filePath), charsetName);
  }

  /**
   * Return the string in file.
   *
   * @param file The file.
   * @return the string in file
   */
  public static String readFile2String(final File file) {
    return readFile2String(file, null);
  }

  /**
   * Return the string in file.
   *
   * @param file        The file.
   * @param charsetName The name of charset.
   * @return the string in file
   */
  public static String readFile2String(final File file, final String charsetName) {
    byte[] bytes = readFile2BytesByStream(file);
    if (bytes == null) return null;
    if (isSpace(charsetName)) {
      return new String(bytes);
    } else {
      try {
        return new String(bytes, charsetName);
      } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
        return "";
      }
    }
  }

  /**
   * Return the bytes in file by stream.
   *
   * @param filePath The path of file.
   * @return the bytes in file
   */
  public static byte[] readFile2BytesByStream(final String filePath) {
    return readFile2BytesByStream(getFileByPath(filePath));
  }

  /**
   * Return the bytes in file by stream.
   *
   * @param file The file.
   * @return the bytes in file
   */
  public static byte[] readFile2BytesByStream(final File file) {
    if (!isFileExists(file)) return null;
    try {
      return is2Bytes(new FileInputStream(file));
    } catch (FileNotFoundException e) {
      e.printStackTrace();
      return null;
    }
  }

  /**
   * Return the bytes in file by channel.
   *
   * @param filePath The path of file.
   * @return the bytes in file
   */
  public static byte[] readFile2BytesByChannel(final String filePath) {
    return readFile2BytesByChannel(getFileByPath(filePath));
  }

  /**
   * Return the bytes in file by channel.
   *
   * @param file The file.
   * @return the bytes in file
   */
  public static byte[] readFile2BytesByChannel(final File file) {
    if (!isFileExists(file)) return null;
    FileChannel fc = null;
    try {
      fc = new RandomAccessFile(file, "r").getChannel();
      ByteBuffer byteBuffer = ByteBuffer.allocate((int) fc.size());
      while (true) {
        if (!((fc.read(byteBuffer)) > 0)) break;
      }
      return byteBuffer.array();
    } catch (IOException e) {
      e.printStackTrace();
      return null;
    } finally {
      try {
        if (fc != null) {
          fc.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Return the bytes in file by map.
   *
   * @param filePath The path of file.
   * @return the bytes in file
   */
  public static byte[] readFile2BytesByMap(final String filePath) {
    return readFile2BytesByMap(getFileByPath(filePath));
  }

  /**
   * Return the bytes in file by map.
   *
   * @param file The file.
   * @return the bytes in file
   */
  public static byte[] readFile2BytesByMap(final File file) {
    if (!isFileExists(file)) return null;
    FileChannel fc = null;
    try {
      fc = new RandomAccessFile(file, "r").getChannel();
      int size = (int) fc.size();
      MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load();
      byte[] result = new byte[size];
      mbb.get(result, 0, size);
      return result;
    } catch (IOException e) {
      e.printStackTrace();
      return null;
    } finally {
      try {
        if (fc != null) {
          fc.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Set the buffer's size.
   * <p>Default size equals 8192 bytes.</p>
   *
   * @param bufferSize The buffer's size.
   */
  public static void setBufferSize(final int bufferSize) {
    sBufferSize = bufferSize;
  }

  private static File getFileByPath(final String filePath) {
    return isSpace(filePath) ? null : new File(filePath);
  }

  private static boolean createOrExistsFile(final String filePath) {
    return createOrExistsFile(getFileByPath(filePath));
  }

  private static boolean createOrExistsFile(final File file) {
    if (file == null) return false;
    if (file.exists()) return file.isFile();
    if (!createOrExistsDir(file.getParentFile())) return false;
    try {
      return file.createNewFile();
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    }
  }

  private static boolean createOrExistsDir(final File file) {
    return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
  }

  private static boolean isFileExists(final File file) {
    return file != null && file.exists();
  }

  private static boolean isSpace(final String s) {
    if (s == null) return true;
    for (int i = 0, len = s.length(); i < len; ++i) {
      if (!Character.isWhitespace(s.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  private static byte[] is2Bytes(final InputStream is) {
    if (is == null) return null;
    ByteArrayOutputStream os = null;
    try {
      os = new ByteArrayOutputStream();
      byte[] b = new byte[sBufferSize];
      int len;
      while ((len = is.read(b, 0, sBufferSize)) != -1) {
        os.write(b, 0, len);
      }
      return os.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
      return null;
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        if (os != null) {
          os.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}
